using System;
using System.Text;
using System.IO;
using BitTorrent.Client.Peer.Exceptions;

namespace BitTorrent.Client.Peer.Message.Standard {
    internal class Handshake : PeerMessage
    {
        public Handshake() {
        }

        public Handshake(BinaryReader reader) {
            DecodeProtocolString(reader);

            reader.BaseStream.Seek(reader.BaseStream.Position + 8, SeekOrigin.Begin);

            DecodeInfoHash(reader);

            DecodePeerId(reader);
        }

        #region Properties

        /// <summary>
        /// String identifier of the protocol
        /// </summary>
        public string ProtocolString { get; set; }

        /// <summary>
        /// 20byte SHA1 hash of the info key in the metainfo file.
        /// </summary>
        public byte[] InfoHash { get; set; }

        /// <summary>
        /// Unique ID for the peer.
        /// </summary>
        public PeerId PeerId { get; set; }

        #endregion

        /// <summary>
        /// Returns the current handshake byte encoded.
        /// </summary>
        /// <returns></returns>
        public override byte[] GetMessage() {
            // One byte limit for size
            if (ProtocolString.Length > 256) {
                throw new PeerMessageException("ProtocolString was larger than 256");
            }

            int length = 1 /* 1byte representing protocol string length */ + ProtocolString.Length +
                         8 /* Reserved bytes */ + InfoHash.Length + PeerId.Length;
            var result = new byte[length];

            using (var memory = new MemoryStream(result)) {
                var writer = new BinaryWriter(memory);

                byte[] bytes = BitConverter.GetBytes(ProtocolString.Length);

                byte pstrLen;
                if (BitConverter.IsLittleEndian) {
                    pstrLen = bytes[0];
                }
                else {
                    pstrLen = bytes[bytes.Length - 1];
                }

                writer.Write(pstrLen);
                writer.Write(Encoding.ASCII.GetBytes(ProtocolString));

                writer.Write(new byte[8]); /// Reserved

                writer.Write(InfoHash);
                writer.Write(PeerId.Bytes);

                writer.Flush();
            }

            return result;
        }

        #region Private

        private void DecodePeerId(BinaryReader reader) {
            var peerId = new byte[20];
            reader.Read(peerId, 0, 20);
            PeerId = new PeerId(peerId);
        }

        private void DecodeInfoHash(BinaryReader reader) {
            var infoHash = new byte[20];
            reader.Read(infoHash, 0, 20);
            InfoHash = infoHash;
        }

        private void DecodeProtocolString(BinaryReader reader) {
            var protocolStrLength = new byte[1];
            reader.Read(protocolStrLength, 0, 1);

            var bytes = new byte[4];
            if (BitConverter.IsLittleEndian) {
                bytes[0] = protocolStrLength[0];
            }
            else {
                bytes[3] = protocolStrLength[0];
            }
            var protocolStrLen = BitConverter.ToInt32(bytes, 0);
            var protocolStr = new byte[protocolStrLen];

            reader.Read(protocolStr, 0, protocolStrLen);

            ProtocolString = Encoding.UTF8.GetString(protocolStr);
        }

        #endregion
    }
}